// Copyright (c) 2012, the Dart project authors.  Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.

part of dart.core;

/**
 * 字符序列。
 *
 * 字符串可以是单行或多行。
 * 单行字符串匹配单引号或双引号，多行字符串匹配三重单引号。
 * 下面是有效的Dart字符串：
 *
 *     'Single quotes';
 *     "Double quotes";
 *     'Double quotes in "single" quotes';
 *     "Single quotes in 'double' quotes";
 *
 *     '''A
 *     multiline
 *     string''';
 *
 *     """
 *     Another
 *     multiline
 *     string""";
 *
 * 字符串是不可改变的。
 * 虽然不能更改字符串，但是可以在字符串上执行操作，然后将结果赋给一个新的字符串。
 *
 *     var string = 'Dart is fun';
 *     var newString = string.substring(0, 5);
 *
 * 你可以使用加号运算符`+`来连接字符串：
 *
 *     'Dart ' + 'is ' + 'fun!'; // 'Dart is fun!'
 *
 * 你也可以使用相邻的字符串来串联：
 *
 *     'Dart ' 'is ' 'fun!';    // 'Dart is fun!'
 *
 * 你可以使用`${}`在字符串中插入Dart表达式的值。
 * 当对标识符求值时，大括号可以省略。
 *
 *     string = 'dartlang';
 *     '$string has ${string.length} letters'; // 'dartlang has 8 letters'
 *
 * 字符串表示Unicode UTF-16编码单元（code unit）的序列。
 * 可以通过[codeUnitAt]或[codeUnits]成员对各个单元进行访问。
 *
 *     string = 'Dart';
 *     string.codeUnitAt(0); // 68
 *     string.codeUnits;     // [68, 97, 114, 116]
 *
 * 字符串的编码单元，可以通过索引操作符访问访问：
 *
 *     string[0];            // 'D'
 *
 * 字符串的字符使用UTF-16编码。
 * 解码UTF-16时，结合surrogate pair（代理编码对），生成Unicode code point
 * （code point的中文*或许*叫代码点，是不同字符集用来表示字符的所有整数的范围，而且是从0开始的）。
 * 接下来是类似Go的术语，使用`rune`生成表示Unicode code point的整数。
 * 使用[runes]属性来获取字符串的rune：
 *
 *     string.runes.toList(); // [68, 97, 114, 116]
 *
 * 对于Basic Multilingual Plane
 * （基本多文种平面，简写BMP或plane 0，又称零号平面。它由代理编码对组成。
 * 零号平面内的字符用4位十六进制数表示，而零号平面外需要5位或6位十六进制数。）
 * 之外的字符，[runes]结合编码对，并返回一个单一的整数。例如，音乐中的G-clef（'𝄞'）符号，
 * Unicode字符使用的rune值0x1D11E由UTF-16代理编码对`0xD834`和`0xDD1E`组成。
 * 使用[codeUnits]返回代理编码对，使用`runes`返回它们的结合值：
 *
 *     var clef = '\u{1D11E}';
 *     clef.codeUnits;         // [0xD834, 0xDD1E]
 *     clef.runes.toList();    // [0x1D11E]
 *
 * 当试图继承或实现String类的时候，会产生一个编译时错误。
 *
 * ## 其它资源
 *
 * 参见[StringBuffer]，高效、递增地构建字符串。
 * 参见[RegExp]使用正则表达式。
 *
 * 也可以查看：
 *
 * * [Dart Cookbook](https://www.dartlang.org/docs/cookbook/#strings)
 *   中关于字符串的实例和技巧。
 * * [Dart Up and Running](https://www.dartlang.org/docs/dart-up-and-running/ch03.html#strings-and-regular-expressions)
 */
abstract class String implements Comparable<String>, Pattern {
  /**
   * 为指定的[charCodes]分配一个新的字符串。
   *
   * [charCodes]可以是UTF-16编码单元或rune。如果字符编码值是16位，会逐字进行复制：
   *
   *     new String.fromCharCodes([68]); // 'D'
   *
   * 如果字符编码值大于16位，它会被分解成一个代理编码对：
   *
   *     var clef = new String.fromCharCodes([0x1D11E]);
   *     clef.codeUnitAt(0); // 0xD834
   *     clef.codeUnitAt(1); // 0xDD1E
   *
   * 如果设置了[start]和[end]，
   * 那么[charCodes]会从`start`的位置开始取值，并且不包括`end`位置的编码。
   * 也就是说，迭代取值的范围区间是：`[start, end)`。
   * 同时，`start`和`end`的值必须满足：
   * `0 <= start <= end <= charCodes.length`。
   */
  external factory String.fromCharCodes(Iterable<int> charCodes,
                                        [int start = 0, int end]);

  /**
   * 为指定的[charCode]分配一个新的字符串。
   *
   * 如果[charCode]表示一个单一的UTF-16 code unit，则分配一个仅包含一个单一编码单元的字符串。
   * 否则，长度[length]为2，编码单元会产生一个代理编码对。可查看[fromCharCodes]的文档。
   *
   * 允许使用代理编码对的一半来创建字符串。
   */
  external factory String.fromCharCode(int charCode);

  /**
   * 返回环境声明变量[name]的字符串值。
   *
   * 环境变量声明由围绕系统编译或运行Dart程序提供。声明是一个Map，key和value均为字符串。
   *
   * 如果[name]未在环境变量中声明，结果用[defaultValue]代替。
   *
   * 获取值的例子：
   *
   *     const String.fromEnvironment("defaultFloo", defaultValue: "no floo")
   *
   * 检查是否声明的例子：
   *
   *     var isDeclared = const String.fromEnvironment("maybeDeclared") != null;
   */
  external const factory String.fromEnvironment(String name,
                                                {String defaultValue});

  /**
   * 获取[index]位置的字符（作为一个仅包含单一编码单元的字符串）。
   *
   * 返回的字符串仅表示一个UTF-16编码单元（可能是代理编码对的一半）。
   * 一个单一的代理编码对的成员是一个无效的UTF-16字符串：
   *
   *     var clef = '\u{1D11E}';
   *     // 这里表示无效的UTF-16字符串，长度为2。
   *     clef[0].codeUnits;      // [0xD834]
   *     clef[1].codeUnits;      // [0xDD1E]
   *
   * 该方法等同于：
   * `new String.fromCharCode(this.codeUnitAt(index))`.
   */
  String operator [](int index);

  /**
   * 返回指定位置[index]的16位UTF-16编码单元。
   */
  int codeUnitAt(int index);

  /**
   * 字符串的长度。
   *
   * 返回字符串中，UTF-16编码单元的数量。
   * 如果字符串包含的字符在Basic Multilingual Plane（零号平面）之外，[runes]的数量可能更小：
   *
   *     'Dart'.length;          // 4
   *     'Dart'.runes.length;    // 4
   *
   *     var clef = '\u{1D11E}';
   *     clef.length;            // 2
   *     clef.runes.length;      // 1
   */
  int get length;

  /**
   * 返回来自字符串的编码单元的哈希码。
   *
   * 它与[operator==]兼容。相同编码单元序列的字符串，具有相同的哈希码。.
   */
  int get hashCode;

  /**
   * 如果other是一个`String`，并且有相同的编码单元序列，返回true。
   *
   * 该方法将对字符串的每个单独的编码单元进行比较。
   * 它并不检查Unicode是否等效。
   * 例如，下面的字符串都表示'Amélie'，但由于它们的编码不同，所以不相等：
   *
   *     'Am\xe9lie' == 'Ame\u{301}lie'; // false
   *
   * 第一个字符串对'é'编码时，将其视作一个单一的Unicode编码单元（也是一个单一的rune）。
   * 而第二个字符串编码时，它被视作字符串'e'与重音符号'◌́'相结合。
   */
  bool operator ==(Object other);

  /**
   * 如果字符串以[other]结束，返回true。例如：
   *
   *     'Dart'.endsWith('t'); // true
   */
  bool endsWith(String other);

  /**
   * 如果字符串以[pattern]的匹配开始，返回true。
   *
   *     var string = 'Dart';
   *     string.startsWith('D');                       // true
   *     string.startsWith(new RegExp(r'[A-Z][a-z]')); // true
   *
   * 如果设置参数[index]，此函数检查以index位置开始的子字符串，
   * 是否以[pattern]的匹配开始：
   *
   *     string.startsWith('art', 1);                  // true
   *     string.startsWith(new RegExp(r'\w{3}'));      // true
   *
   * [index]不能为负数或大于[length]。
   *
   * 如果[index]大于0，包含'^'的[RegExp]匹配失败。
   * 因为Pattern将字符串视作一个整体，并不提取[index]位置开始的子字符串：
   *
   *     string.startsWith(new RegExp(r'^art'), 1);    // false
   *     string.startsWith(new RegExp(r'art'), 1);     // true
   */
  bool startsWith(Pattern pattern, [int index = 0]);

  /**
   * 返回字符串中，从[start]（包括）开始，[pattern]第一次匹配到的位置：
   *
   *     var string = 'Dartisans';
   *     string.indexOf('art');                     // 1
   *     string.indexOf(new RegExp(r'[A-Z][a-z]')); // 0
   *
   * 如果未匹配到字符串，返回-1；
   *
   *     string.indexOf(new RegExp(r'dart'));       // -1
   *
   * [start]默认值为`0`，并且不能为负数或大于[length]。
   */
  int indexOf(Pattern pattern, [int start]);

  /**
   * 返回字符串中，从[start]（包括）至`0`，[pattern]第一次匹配到的位置：
   *
   *     var string = 'Dartisans';
   *     string.lastIndexOf('a');                    // 6
   *     string.lastIndexOf(new RegExp(r'a(r|n)'));  // 6
   *
   * 如果未匹配到字符串，返回-1；
   *
   *     string.lastIndexOf(new RegExp(r'DART'));    // -1
   *
   * [start]默认值为[length]，并且不能为负数或大于[length]。
   */
  int lastIndexOf(Pattern pattern, [int start]);

  /**
   * 如果字符串为空，返回true。
   */
  bool get isEmpty;

  /**
   * 如果字符串不为空，返回true。
   */
  bool get isNotEmpty;

  /**
   * 将`this`与[other]串联，创建一个新的字符串。
   *
   *     'dart' + 'lang'; // 'dartlang'
   */
  String operator +(String other);

  /**
   * 返回`this`从[startIndex]（包括）到[endIndex]（不包括）的子字符串。
   *
   *     var string = 'dartlang';
   *     string.substring(1);    // 'artlang'
   *     string.substring(1, 4); // 'art'
   */
  String substring(int startIndex, [int endIndex]);

  /**
   * 去掉字符串开始和末尾的空白字符，返回一个新的字符串。
   *
   *     '\tDart is fun\n'.trim(); // 'Dart is fun'
   *
   * 如果开始或末尾没有空白字符，返回原始字符串本身：
   *
   *     var str1 = 'Dart';
   *     var str2 = str1.trim();
   *     identical(str1, str2);    // true
   *
   * 空白字符包括Unicode中White_Space属性定义的字符（6.2或更高版本），
   * 以及BOM字符0xFEFF。
   *
   * 下面是被删除字符的列表（6.2版本）：
   *
   *     0009..000D    ; White_Space # Cc   <control-0009>..<control-000D>
   *     0020          ; White_Space # Zs   SPACE
   *     0085          ; White_Space # Cc   <control-0085>
   *     00A0          ; White_Space # Zs   NO-BREAK SPACE
   *     1680          ; White_Space # Zs   OGHAM SPACE MARK
   *     180E          ; White_Space # Zs   MONGOLIAN VOWEL SEPARATOR
   *     2000..200A    ; White_Space # Zs   EN QUAD..HAIR SPACE
   *     2028          ; White_Space # Zl   LINE SEPARATOR
   *     2029          ; White_Space # Zp   PARAGRAPH SEPARATOR
   *     202F          ; White_Space # Zs   NARROW NO-BREAK SPACE
   *     205F          ; White_Space # Zs   MEDIUM MATHEMATICAL SPACE
   *     3000          ; White_Space # Zs   IDEOGRAPHIC SPACE
   *
   *     FEFF          ; BOM                ZERO WIDTH NO_BREAK SPACE
   */
  String trim();

  /**
   * 去掉字符串开始的空白字符，返回新的字符串。
   *
   * 同[trim]，但是只删除开始的空白字符。
   */
  String trimLeft();

  /**
   * 去掉字符串末尾的空白字符，返回新的字符串。
   *
   * 同[trim]，但是只删除末尾的空白字符。
   */
  String trimRight();

  /**
   * 串联`this`字符串`times`次，返回一个新的字符串。
   *
   * `str * n`的结果等同于
   * `str + str + ...`（n次）`... + str`。
   *
   * 如果[times]是0或负数，返回一个空的字符串。
   */
  String operator *(int times);

  /**
   * 如果长度小于[width]，在字符串左边填充字符串。
   *
   * 如果`this`字符串长度小于[width]，则在字符串左边的每个位置填充一次`padding`，
   * 然后返回一个新的字符串。
   *
   * 如果[width]已经小于或等于`this.length`，则不填充。`width`为负值时，视作0。
   *
   * 如果[padding]长度不为1，结果的长度则不为`width`。
   * 当填充一个长字符串表示单一字符时，这很有用。
   * 比如`"&nbsp;"`或`"\u{10002}"`。
   * 在这种情况下，使用者应该确保`this.length`是字符串正确的估量长度。
   */
  String padLeft(int width, [String padding = ' ']);

  /**
   * 如果长度小于[width]，在字符串右边填充字符串。
   *
   * 如果`this`字符串长度小于[width]，则在字符串右边的每个位置填充一次`padding`，
   * 然后返回一个新的字符串。
   *
   * 如果[width]已经小于或等于`this.length`，则不填充。`width`为负值时，视作0。
   *
   * 如果[padding]长度不为1，结果的长度则不为`width`。
   * 当填充一个长字符串表示单一字符时，这很有用。
   * 比如`"&nbsp;"`或`"\u{10002}"`。
   * 在这种情况下，使用者应该确保`this.length`是字符串正确的估量长度。
   */
  String padRight(int width, [String padding = ' ']);

  /**
   * 如果字符串包含[other]的匹配，返回true：
   *
   *     var string = 'Dart strings';
   *     string.contains('D');                     // true
   *     string.contains(new RegExp(r'[A-Z]'));    // true
   *
   * 如果设置[startIndex]参数，则方法会在索引的地方，以及之后位置进行匹配：
   *
   *     string.contains('X', 1);                  // false
   *     string.contains(new RegExp(r'[A-Z]'), 1); // false
   *
   * [startIndex]不能为负值或大于[length]。
   */
  bool contains(Pattern other, [int startIndex = 0]);

  /**
   * 在字符串中，从[startIndex]开始，将[from]第一次出现的匹配替换为字符串[to]，
   * 返回一个新的字符串：
   *
   *     '0.0001'.replaceFirst(new RegExp(r'0'), ''); // '.0001'
   *     '0.0001'.replaceFirst(new RegExp(r'0'), '7', 1); // '0.7001'
   */
  String replaceFirst(Pattern from, String to, [int startIndex = 0]);

  /**
   * 替换字符串中，[from]第一个出现的匹配。
   *
   * 从[startIndex]开始，提取`this`字符串中第一个[pattern]匹配，
   * 将其替换为调用[replace]（使用之前的匹配对象作为参数）的结果，返回一个新的字符串。
   *
   * 如果调用`replace`的返回值不是[String]，
   * 那么将调用`toString`方法转换为`String`，替换方法的返回值必须是字符串。
   */
  String replaceFirstMapped(Pattern from, String replace(Match match),
                            [int startIndex = 0]);

  /**
   * 将所有[from]匹配到的子字符串替换为[replace]。
   *
   * 将[from]匹配到的非重叠的子字符串（通过`from.allMatches(thisString)`迭代）
   * 替换为原义字符串[replace]，返回一个新的字符串。
   *
   *     'resume'.replaceAll(new RegExp(r'e'), 'é'); // 'résumé'
   *
   * 注意，它并不解释字符串[replace]。
   * 如果替换依赖匹配（如[RegExp]捕获到的group），那么使用[replaceAllMapped]方法来代替。
   */
  String replaceAll(Pattern from, String replace);

  /**
   * 将所有[from]匹配到的子字符串，替换为Match计算后的值。
   *
   * 将[from]匹配到的非重叠子字符串（通过`from.allMatches(thisString)`迭代）
   * 替换为[replace]方法（参数为对应的[Match]对象）的调用结果，返回一个新的字符串。
   *
   * 该方法可用于，使用依赖于Match的新内容替换匹配。
   * 而不同于[replaceAll]，替换的字符串全部相同。
   *
   * 调用[replace]函数时，参数为Pattern生成的[Match]，
   * 并且函数的调用结果被用于替换。
   *
   * 下面的函数定义为，使用[replaceAllMapped]，
   * 将字符串中每个词转换为简化的'Pig Latin'：
   *
   *     pigLatin(String words) => words.replaceAllMapped(
   *         new RegExp(r'\b(\w*?)([aeiou]\w*)', caseSensitive: false),
   *         (Match m) => "${m[2]}${m[1]}${m[1].isEmpty ? 'way' : 'ay'}");
   *
   *     pigLatin('I have a secret now!'); // 'Iway avehay away ecretsay ownay!'
   */
  String replaceAllMapped(Pattern from, String replace(Match match));

  /**
   * 将[start]至[end]的子字符串替换为[replacement]。
   *
   * 返回一个新的字符串，等价于：
   *
   *     this.substring(0, start) + replacement + this.substring(end)
   *
   * 索引[start]和[end]必须指定一个`this`字符串的有效范围，
   * 也就是说，`0 <= start <= end <= this.length`。
   * 如果[end]是`null`，它将默认为[length]。
   */
  String replaceRange(int start, int end, String replacement);

  /**
   * 以[pattern]匹配到的位置分割字符串，并返回一个子字符串的List。
   *
   * 查找字符串中，`pattern`所有的匹配，并返回匹配之间的子字符串的List。
   *
   *     var string = "Hello world!";
   *     string.split(" ");                      // ['Hello', 'world!'];
   *
   * 字符串开始和结束位置的空匹配会被忽略。
   *
   *     var string = "abba";
   *     string.split(new RegExp(r"b*"));        // ['a', 'a']，而不是['', 'a', 'a', '']
   *     string.split(new RegExp(r"a*"));        // ['', 'b', 'b', '']，而不是['b', 'b']
   *
   * 字符串为空时，如果`pattern`匹配空字符串，那么结果为空。
   * 如果`pattern`并没有匹配，结果为`[""]`。
   *
   *     var string = '';
   *     string.split('');                       // []
   *     string.split("a");                      // ['']
   *
   * 使用空的Pattern分割字符串时，会将字符串分割为单个的编码单元字符串。
   *
   *     var string = 'Pub';
   *     string.split('');                       // ['P', 'u', 'b']
   *
   *     string.codeUnits.map((unit) {
   *       return new String.fromCharCode(unit);
   *     }).toList();                            // ['P', 'u', 'b']
   *
   * 分割的边界线是UTF-16编码单元，而不是rune。
   * 也就是说，单个的rune会被分割成2个值：
   *
   *     //字符串由2个编码单元组成，但是只有1个rune
   *     string = '\u{1D11E}';
   *     string.split('').length;                 // 2，代理编码对有2个值
   *
   * 因此，如果获取的字符串列表包含字符串单个的rune，
   * 那么不应该使用split方法。
   * 你可以如下使用map方法代替，对每个rune作映射：
   *
   *     string.runes.map((rune) => new String.fromCharCode(rune)).toList();
   */
  List<String> split(Pattern pattern);

  /**
   * 分割字符串，转换部分内容，并将它们组合成一个新的字符串。
   *
   * [pattern]用于将字符串分割成部分，分开匹配。
   *
   * 每个匹配通过调用[onMatch]转换成字符串。
   * 如果[onMatch]未设置，匹配的字符串会直接被使用。
   *
   * 每个非匹配部分通过调用[onNonMatch]来进行转换。
   * 如果[onNonMatch]未设置，非匹配部分会直接被使用。
   *
   * 然后，所有转换的部分被组合成结果字符串。
   *
   *     'Eats shoots leaves'.splitMapJoin((new RegExp(r'shoots')),
   *         onMatch:    (m) => '${m.group(0)}',
   *         onNonMatch: (n) => '*'); // *shoots*
   */
  String splitMapJoin(Pattern pattern,
                      {String onMatch(Match match),
                       String onNonMatch(String nonMatch)});

  /**
   * 返回一个只读的List，包含字符串中所有的UTF-16编码单元。
   */
  List<int> get codeUnits;

  /**
   * 返回字符串中Unicode代码点的[Iterable]。
   *
   * 如果字符串包含代理编码对，它们会使用整数被组合，并通过迭代器返回。
   * 不匹配的代理编码对会被分成两部分，像有效的16位编码单元一样来处理。
   */
  Runes get runes;

  /**
   * 将字符串中所有的字符转换为小写。
   * 如果字符串已经全部是小写，返回[:this:]。
   *
   *     'ALPHABET'.toLowerCase(); // 'alphabet'
   *     'abc'.toLowerCase();      // 'abc'
   *
   * 此函数使用语言无关的Unicode映射，因此仅用于部分语言。
   */
  // TODO(floitsch): document better. (See EcmaScript for description).
  String toLowerCase();

  /**
   * 将字符串中所有的字符转换为大写。
   * 如果字符串已经全部是大写，返回[:this:]。
   *
   *     'alphabet'.toUpperCase(); // 'ALPHABET'
   *     'ABC'.toUpperCase();      // 'ABC'
   *
   * 此函数使用语言无关的Unicode映射，因此仅用于部分语言。
   */
  // TODO(floitsch): document better. (See EcmaScript for description).
  String toUpperCase();
}

/**
 * [String]的rune（整型Unicode代码点）。
 */
class Runes extends Iterable<int> {
  final String string;
  Runes(this.string);

  RuneIterator get iterator => new RuneIterator(string);

  int get last {
    if (string.length == 0) {
      throw new StateError('No elements.');
    }
    int length = string.length;
    int code = string.codeUnitAt(length - 1);
    if (_isTrailSurrogate(code) && string.length > 1) {
      int previousCode = string.codeUnitAt(length - 2);
      if (_isLeadSurrogate(previousCode)) {
        return _combineSurrogatePair(previousCode, code);
      }
    }
    return code;
  }

}

// Is then code (a 16-bit unsigned integer) a UTF-16 lead surrogate.
bool _isLeadSurrogate(int code) => (code & 0xFC00) == 0xD800;

// Is then code (a 16-bit unsigned integer) a UTF-16 trail surrogate.
bool _isTrailSurrogate(int code) => (code & 0xFC00) == 0xDC00;

// Combine a lead and a trail surrogate value into a single code point.
int _combineSurrogatePair(int start, int end) {
  return 0x10000 + ((start & 0x3FF) << 10) + (end & 0x3FF);
}

/**
 * 用于读取Dart字符串rune（整型Unicode代码点）的迭代器[Iterator]。
 */
class RuneIterator implements BidirectionalIterator<int> {
  /** 被迭代的字符串。 */
  final String string;
  /** Position before the current code point. */
  int _position;
  /** Position after the current code point. */
  int _nextPosition;
  /**
   * Current code point.
   *
   * If the iterator has hit either end, the [_currentCodePoint] is null
   * and [: _position == _nextPosition :].
   */
  int _currentCodePoint;

  /** 创建一个位于字符串开始位置的迭代器。 */
  RuneIterator(String string)
      : this.string = string, _position = 0, _nextPosition = 0;

  /**
   * 创建一个位于字符串[index]位置编码单元的迭代器。
   *
   * 在创建后，并没有[current]值。
   * [moveNext]将使用rune从[index]开始的current值，
   * 并且[movePrevious]会移动到rune末尾，作为[index]前面的current值。
   *
   * [index]索引位置不能在一个代理编码对中间。
   */
  RuneIterator.at(String string, int index)
      : string = string, _position = index, _nextPosition = index {
    RangeError.checkValueInInterval(index, 0, string.length);
    _checkSplitSurrogate(index);
  }

  /** Throw an error if the index is in the middle of a surrogate pair. */
  void _checkSplitSurrogate(int index) {
    if (index > 0 && index < string.length &&
        _isLeadSurrogate(string.codeUnitAt(index - 1)) &&
        _isTrailSurrogate(string.codeUnitAt(index))) {
      throw new ArgumentError('Index inside surrogate pair: $index');
    }
  }

  /**
   * 返回字符串中当前rune的开始位置。
   *
   * 如果[current]rune为null，返回null。
   */
  int get rawIndex => (_position != _nextPosition) ? _position : null;

  /**
   * 重置rune迭代器到字符串的指定位置。
   *
   * 设置[rawIndex]为负数，或大于等于[:string.length:]的值时，会抛出错误。
   * 同样，不能将索引设置在一个代理编码对中间。
   *
   * 设置位置为字符串末尾时，会同时设置[current]为null。
   */
  void set rawIndex(int rawIndex) {
    RangeError.checkValidIndex(rawIndex, string, "rawIndex");
    reset(rawIndex);
    moveNext();
  }

  /**
   * 重置迭代器到字符串的指定位置。
   *
   * 充值并不会更改[current]。
   * 你必须调用[moveNext]使rune位于索引处，
   * 或调用[movePrevious]使current位于最后的rune之前。
   *
   * 设置[rawIndex]为负数，或大于等于[:string.length:]的值时，会抛出错误。
   * 同样，不能将索引设置在一个代理编码对中间。
   */
  void reset([int rawIndex = 0]) {
    RangeError.checkValueInInterval(rawIndex, 0, string.length, "rawIndex");
    _checkSplitSurrogate(rawIndex);
    _position = _nextPosition = rawIndex;
    _currentCodePoint = null;
  }

  /**
   * 在字符串中，当前位置的rune（整型Unicode代码点）。
   */
  int get current => _currentCodePoint;

  /**
   * 构成当前rune的编码单元的数量。
   *
   * 如果[current]为null，返回0。
   */
  int get currentSize => _nextPosition - _position;

  /**
   * 包含当前rune的字符串。
   *
   * 对于零号平面之外的rune，返回一个长度为2，包含2个编码单元的字符串。
   *
   * 如果[current]为null，返回null。
   */
  String get currentAsString {
    if (_position == _nextPosition) return null;
    if (_position + 1 == _nextPosition) return string[_position];
    return string.substring(_position, _nextPosition);
  }

  bool moveNext() {
    _position = _nextPosition;
    if (_position == string.length) {
      _currentCodePoint = null;
      return false;
    }
    int codeUnit = string.codeUnitAt(_position);
    int nextPosition = _position + 1;
    if (_isLeadSurrogate(codeUnit) && nextPosition < string.length) {
      int nextCodeUnit = string.codeUnitAt(nextPosition);
      if (_isTrailSurrogate(nextCodeUnit)) {
        _nextPosition = nextPosition + 1;
        _currentCodePoint = _combineSurrogatePair(codeUnit, nextCodeUnit);
        return true;
      }
    }
    _nextPosition = nextPosition;
    _currentCodePoint = codeUnit;
    return true;
  }

  bool movePrevious() {
    _nextPosition = _position;
    if (_position == 0) {
      _currentCodePoint = null;
      return false;
    }
    int position = _position - 1;
    int codeUnit = string.codeUnitAt(position);
    if (_isTrailSurrogate(codeUnit) && position > 0) {
      int prevCodeUnit = string.codeUnitAt(position - 1);
      if (_isLeadSurrogate(prevCodeUnit)) {
        _position = position - 1;
        _currentCodePoint = _combineSurrogatePair(prevCodeUnit, codeUnit);
        return true;
      }
    }
    _position = position;
    _currentCodePoint = codeUnit;
    return true;
  }
}
